/*
 * Copyright (c) 2010, Yawning <yawninglol at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.cat;

import yawning.mew.BuffsDebuffs;
import yawning.mew.BuffsDebuffs.Buff;
import yawning.mew.BuffsDebuffs.Debuff;
import yawning.mew.MewView;
import yawning.mew.PropertiesBase;
import yawning.mew.PropertiesBase.IProperty;
import yawning.mew.character.CharacterModel;
import yawning.mew.character.Druid;
import yawning.mew.character.Druid.Glyph;
import yawning.mew.character.Druid.Talent;
import yawning.mew.character.MetaGem;
import yawning.mew.character.Toon;

/**
 * Model Backend Infrastructure
 *
 * @author Yawning <yawninglol at gmail.com>
 */
public class CatModel extends CharacterModel {
	private enum BackendProperty implements IProperty {
		BLOODINTHEWATERTIME("BloodInTheWaterTime", "0.0"),	// Amount of time Blood in the Water is active
		BUG_PREDATORY_INSTINCTS("Bug.PredatoryInstincts", "false"),			// 4.0.1 Druids with Predatory Instincts retain benefit.
		BUG_BLOODINTHEWATER_RIP_DMG("Bug.BloodInTheWaterRipDmg", "true");	// 4.0.1 Rip refresh via Blood in the Water loses static damage.

		private final String propertyKey;
		private final String defaultValue;

		private BackendProperty(String k) {
			this(k, null);
		}

		private BackendProperty(String k, String v) {
			propertyKey = k;
			defaultValue = v;
		}

		public String getKey() {
			return this.propertyKey;
		}

		public String getDefault() {
			return this.defaultValue;
		}
	}

	protected MewView view;					// View for monitoring.

	// Common backend internals.
	protected LevelDepCat levelDep;			// Level dependent parameters.
	protected Toon druid;					// Character stats.
	protected BuffsDebuffs buffsDebuffs;	// Buffs/Debuffs.

	// Backend support.
	private int roundPrecision = 5;			// Default rounding precision when logging.
	protected PropertiesBase config;			// Backend Properties.

	// Druid stats.
	protected int baseEnergyCap = 100;

	protected double pBonusBiteCrit;

	protected double pPrimalFury;
	protected double pFurySwipes;
	protected double pBloodInTheWater;
	protected double oocPPM;
	protected double pOoCProc;

	// Multipliers
	protected double multCrit = 2.0;
	protected double multSpellCrit;
	protected double multRoar;
	protected double multPhysVuln;
	protected double multDamageMult;
	protected double multMangle = 1.3;
	protected double multShredRnT;
//	protected double multBleedMastery;
	protected double multBleedDarkIntent;
	protected double multBiteFeralAggression;
	protected double multRipGlyph;
	protected double multFurySwipes = 3.10;
	protected double multTigersFury = 1.15;
	protected double multToTT = 1.0;
	protected double multTargetBaseArmor;

	protected int nrFeralFFStacksPerCast;

	// Costs.
	protected int mangleCost = 35;
	protected int rakeCost = 35;
	protected int shredCost = 40;
	protected int ripCost = 30;
	protected int srCost = 25;
	protected int biteCost = 35;
	protected int ravageCost = 60;
	protected int ravageCostStampede;
	protected int feralChargeCost = 10;
	protected int skullBashCost = 25;

	// Durations.
	protected int oocBuffDuration = 10;
	protected int feralFFDebuffDuration = 60 * 5;
	protected int feralFFCDDuration = 6;
	protected int berserkBuffDuration;
	protected int berserkCDDuration = 180;
	protected int tigersFuryBuffDuration = 6;
	protected int tigersFuryCDDuration;
	protected int mangleDebuffDuration = 60;
	protected int feralChargeCDDuration = 30;
	protected int skullBashCDDuration;
	protected int baseRakeDebuffDuration;
	protected int baseRipDebuffDuration;
	protected int roarInitialDuration;
	protected int roarCpBonus = 5;
	protected int furySwipesCDDuration = 3;
	protected int shredRipExtDuration;
	protected double baseRakeTickInterval = 3.0;
	protected double baseRipTickInterval = 2.0;
	protected double stampedeBuffDuration;

	protected int tottBuffDuration = 6;
	protected int tottCDDuration = 30;

	protected int heroismBuffDuration = 40;
	protected int heroismCDDuration = 600;

	protected int unholyFrenzyBuffDuration = 30;
	protected int unholyFrenzyCDDuration = 180;

	protected int shatteringThrowDebuffDuration = 10;
	protected int shatteringThrowCDDuration = 300;

	// Other talents.
	protected int primalMadnessEnergyGain;
	protected int kotjEnergyGain;

	// Energy regeneration.
//	protected double energyRegenerationRate;
//	protected double energyTickInterval;

	// Armor related values.
	protected double multTargetArmor;
	protected double debuffShatteringThrow = 0.8; // Model will set this.

	protected double targetArmor;

//	// External buffs.  (I expect the model to set these.)
//	protected double buffHeroism = 1.0;
//	protected double buffUnholyFrenzy = 1.0;

	// Set bonuses.
	protected double tier_10_4pc_rakeMult;
	protected double tier_11_2pc_rakeMult;
	protected double tier_11_4pc_apMult;
	protected double tier_11_4pc_buffStacks;
	protected double tier_11_4pc_buffDuration = 30;

	// Bug specific behavior
	protected int bugBloodInTheWaterRipDmg = 0;

	CatModel(Toon t, PropertiesBase config) {
		super(t, new LevelDepCat(t.level));
		this.druid = t;
		this.buffsDebuffs = new BuffsDebuffs(config.getProperties());
		this.levelDep = (LevelDepCat)mLevelDep;
		this.config = config;
		calculateBaseStats();
	}

	/**
	 * Get Feast of Flesh duration.
	 */
	protected double getBloodInTheWaterTime() {
		return config.getDouble(BackendProperty.BLOODINTHEWATERTIME);
	}

	/**
	 * Round a double to the preferred precision.
	 * @param x Double to round.
	 * @return Rounded double.
	 */
	protected double round(double x) {
		double factor = Math.pow(10, roundPrecision);
		return (double) ((int)(x * factor + 0.5)) / factor;
	}

	// Cats get AP from Strength and Agility
    @Override
	public double getAttackPower() {
		double attackPower = ((getAgility() - 10) * 2 + (getStrength() - 10) * 2 + mToon.attackPower + bonusAttackPower) * apMult;
		if (tier_11_4pc_buffStacks > 0) attackPower *= (1 + tier_11_4pc_buffStacks * tier_11_4pc_apMult);
		return attackPower;
	}

	// Cats have weapon speed forced to 1.0
	@Override
	public double getWeaponDamage() {
		return super.getWeaponDamage() / mToon.weaponSpeed * 1000;
	}

	// Cats have weapon speed forced to 1.0
	@Override
	public double getMeleeInterval() {
		return 1 / getHaste() / meleeHasteMult;
	}

	/**
	 * Calculates the base stats for a given Druid.
	 */
	private void calculateBaseStats() {
		// Agi, Str, AP.
		double statMult = buffsDebuffs.isBuff(Buff.STATMULT) ? 1.05 : 1;
		agiMult *= statMult * (druid.isArmorSpec ? 1.05: 1);
		strMult *= statMult;

		int buffAgiStr = buffsDebuffs.isBuff(Buff.AGISTR) ? levelDep.getBuffAgiStr(): 0;
		bonusAgility += buffAgiStr;
		bonusStrength += buffAgiStr;

        // Cat form AP:
        //  * 2 AP per Agi or Str, first 10 in each are not counted.
        //  * Level * 2 AP for being in cat form.
        //  * Talents/Buffs apply multiplicatively after.
		bonusAttackPower +=  druid.level * 2;
		apMult *= (druid.mTalentSpec == Druid.TalentSpec.FERAL ? 1.25 : 1);
		double hotwMult = druid.getTalent(Talent.HEARTOFTHEWILD) * 0.03;
		hotwMult += (druid.getTalent(Talent.HEARTOFTHEWILD) > 1) ? 0.01 : 0.0;
		apMult *= 1 + hotwMult;
		apMult *= buffsDebuffs.isBuff(Buff.APMULT) ? 1.1 : 1;

		// Crit
		bonusMeleeCrit += levelDep.getBaseCrit() + ((druid.getTalent(Talent.MASTERSHAPESHIFTER) > 0) ? 0.04 : 0);
		bonusMeleeCrit += (buffsDebuffs.isBuff(Buff.CRIT) ? 0.05 : 0);
		pBonusBiteCrit = druid.getTalent(Talent.RENDANDTEAR) * 0.08;
		pBonusBiteCrit += (druid.getTalent(Talent.RENDANDTEAR) > 1) ? 0.01 : 0;

		// Haste
		hasteMult *= ((buffsDebuffs.isBuff(Buff.DARKINTENT)) ? 1.03 : 1);
		meleeHasteMult *= (buffsDebuffs.isBuff(Buff.MELEEHASTE) ? 1.1 : 1);

		// Multipliers
		multCrit *= ((druid.mMetaGem == MetaGem.RELENTLESS) ? 1.03 : 1);
		multSpellCrit = 1.5 * (druid.mMetaGem == MetaGem.RELENTLESS ? 1.03 : 1);
		multRoar = 1.5 + ((druid.hasGlyph(Glyph.SAVAGEROAR)) ? 0.05 : 0);
		multPhysVuln = ((buffsDebuffs.isDebuff(Debuff.PHYSVULN)) ? 1.04 : 1);
		multDamageMult = ((buffsDebuffs.isBuff(Buff.DAMAGEMULT)) ? 1.03 : 1);
		double bonusRnT = druid.getTalent(Talent.RENDANDTEAR) * 0.07;
		bonusRnT -= (druid.getTalent(Talent.RENDANDTEAR) > 1) ? 0.01 : 0;
		multShredRnT = ((druid.getTalent(Talent.RENDANDTEAR) > 0) ? 1 + bonusRnT : 1);
		multBleedDarkIntent = ((buffsDebuffs.isBuff(Buff.DARKINTENT)) ? 1.09 : 1);
		multBiteFeralAggression = 1 + druid.getTalent(Talent.FERALAGGRESSION) * 0.05;
		multRipGlyph = (druid.hasGlyph(Glyph.RIP) ? 1.15 : 1);

		// Durations
		berserkBuffDuration = 15 + (druid.hasGlyph(Glyph.BERSERK) ? 5 : 0);
		baseRakeDebuffDuration = 9 + (druid.getTalent(Talent.ENDLESSCARNAGE) * 3);
		baseRipDebuffDuration = 16;
		roarInitialDuration = 9 + (druid.getTalent(Talent.ENDLESSCARNAGE) * 4);
		shredRipExtDuration = (druid.hasGlyph(Glyph.SHRED) ? 2 : 0);
		stampedeBuffDuration = (druid.getTalent(Talent.STAMPEDE) * 4);
		tigersFuryCDDuration = 30 - (druid.hasGlyph(Glyph.TIGERSFURY) ? 3 : 0);
		skullBashCDDuration = 60 - (druid.getTalent(Talent.BRUTALIMPACT) * 25);

		// Energy regen talents.
		primalMadnessEnergyGain = druid.getTalent(Talent.PRIMALMADNESS) * 10;
		kotjEnergyGain = druid.getTalent(Talent.KINGOFTHEJUNGLE) * 20;

		pPrimalFury = druid.getTalent(Talent.PRIMALFURY) * 0.5;
		pFurySwipes = druid.getTalent(Talent.FURYSWIPES) * 0.05;
		pBloodInTheWater = druid.getTalent(Talent.BLOODINTHEWATER) * 0.5;
		oocPPM = 3.5;
		pOoCProc = oocPPM * (1.0/60);

		nrFeralFFStacksPerCast = 1 + druid.getTalent(Talent.FERALAGGRESSION);

		// Varaible costs
		ravageCostStampede = ravageCost - (ravageCost / 2 * druid.getTalent(Talent.STAMPEDE));

		// Set bonuses.
		//
		// Tier 10:
		// Druid T10 Feral 2P Bonus - Your Swipe (Bear) and Lacerate abilities deal 20% additional damage and the cost of your Rip ability is reduced by 10 energy.
		// Druid T10 Feral 4P Bonus - Your Enrage ability no longer decreases your armor and instead decreases all damage taken by 12%, and increases the damage done by your Rake ability by 10%.
		//
		// Tier 11:
		// Druid T11 Feral 2P Bonus - Increases the periodic damage done by your Rake and Lacerate abilities by 10%.
		// Druid T11 Feral 4P Bonus - Each time you use Mangle (Cat) you gain a 1% increase to attack power for 30 sec stacking up to 3 times, and the duration of your Survival Instinct ability is increased by 50%.
		//
		ripCost -= druid.tier_10_2pc ? 10 : 0;
		tier_10_4pc_rakeMult = druid.tier_10_4pc ? 1.1 : 1.0;
		tier_11_2pc_rakeMult = druid.tier_11_2pc ? 1.1 : 1.0;
		tier_11_4pc_apMult = druid.tier_11_4pc ? 0.01 : 0.0;
		tier_11_4pc_buffStacks = 0;

		// Blizzard bugs
		multCrit *= (config.getBool(BackendProperty.BUG_PREDATORY_INSTINCTS)) ? 1.1 : 1;
		bugBloodInTheWaterRipDmg = (config.getBool(BackendProperty.BUG_BLOODINTHEWATER_RIP_DMG)) ? 52 : 0;
	}

	public final double getBleedMastery() {
		return 1 + getMastery() * 0.03125;
	}

	// Energy regeneration scales with haste.
	//
	// The formula as far as I can tell is thus:
	// 100 / ((BaseTimeToEnergyCap) / (1 + haste%/ 100)) e/sec
	public final double getEnergyRegenRate() {
		return 10 * getHaste();
	}

	public final double getEnergyTickInterval() {
		return 1 / getEnergyRegenRate();
	}

	public final int getRakeDebuffDuration() {
		return baseRakeDebuffDuration;
	}

	public final double getRakeTickInterval() {
		return baseRakeTickInterval;
	}

	public final int getRipDebuffDuration() {
		return baseRipDebuffDuration;
	}

	public final double getRipTickInterval() {
		return baseRipTickInterval;
	}

	/**
	 * Calculate the target's effective armor damage reduction.
	 */
	protected void calculateTargetArmor(int nrFeralFFStacks, boolean shattered) {
		int level = druid.level;

		// Detailed formulas from FrameXML/PaperDollFrame.lua (Thanks Blizzard!)
		// Level 1-59: DR% = Armor / (Armor + 400 + 85 * AttackLevel)
		// Level 60-80: DR % = Armor / (Armor + 400 + 85 * (AttackerLevel + 4.5 * (AttackerLevel - 59)))
		// Level 81-85: DR % = Armor / (Armor + 400 + 85 * (AttackerLevel + 4.5 * (AttackerLevel - 59) + 20 * (AttackerLevel - 80)))
		double baseArmor = levelDep.getMobArmor();
		targetArmor = baseArmor * (1 - 0.04 * nrFeralFFStacks) * ((shattered) ? debuffShatteringThrow: 1);
		if (level < 60) {
			multTargetArmor = targetArmor / (targetArmor + 400 + 85 * level);
			multTargetBaseArmor = baseArmor / (baseArmor + 400 + 85 * level);
		} else if (level <= 80) {
			multTargetArmor = targetArmor / (targetArmor + 400 + 85 * (level + 4.5 * (level - 59)));
	        multTargetBaseArmor = baseArmor / (baseArmor + 400 + 85 * (level + 4.5 * (level - 59)));
		} else {
			multTargetArmor = targetArmor / (targetArmor + 400 + 85 * (level + 4.5 * (level - 59) + 20 * (level - 80)));
	        multTargetBaseArmor = baseArmor / (baseArmor + 400 + 85 * (level + 4.5 * (level - 59) + 20 * (level - 80)));
		}
	}

	@Override
	public String toString() {
		return config.valuesToString(BackendProperty.values());
	}
}
